package cate.game.videohall.po;

import cate.common.table.d.GDVideoHall;
import cate.common.util.GameResult;
import cate.game.GameBody;
import cate.game.db.mongo.kv.KvMongo;
import cate.game.play.proce.play.PlayPO;
import cate.game.role.Role;
import cate.game.videohall.vo.VideoGroupVO;
import cate.game.videohall.vo.VideoVO;
import easy.java.dev.note.NoteField;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

public class VideoHallSPO extends KvMongo {
    @NoteField(value = "录像快照组")
    public List<VideoGroupPO> groups;

    @NoteField("<uid, 用到的组>")
    public Map<String, List<Integer>> usedReplays;

    @NoteField("<uid, 收藏的玩家uid>")
    public Map<String, List<String>> collectReplays;

    @NoteField(value = "被播放次数")
    public Map<String, Integer> playedReplays;

    @NoteField(value = "被分享次数")
    public Map<String, Integer> sharedReplays;

    @NoteField(value = "被点赞次数")
    public Map<String, Integer> admiredReplays;

    @Override
    public String getKeyPrefix() {
        return KvMongo.KEY_VIDEO_HALL;
    }

    @Override
    public void initialize() {
        if (groups == null) {
            groups = new CopyOnWriteArrayList<>();
        }
        groups.forEach(VideoGroupPO::initialize);
        if (usedReplays == null) {
            usedReplays = new HashMap<>();
        }
        if (collectReplays == null) {
            collectReplays = new HashMap<>();
        }
        if (playedReplays == null) {
            playedReplays = new HashMap<>();
        }
        if (sharedReplays == null) {
            sharedReplays = new HashMap<>();
        }
        if (admiredReplays == null) {
            admiredReplays = new HashMap<>();
        }
        usedReplays.entrySet().removeIf(e ->
                CollectionUtils.isEmpty(e.getValue()) ||
                        !exist(e)
        );
        collectReplays.entrySet().removeIf(e -> CollectionUtils.isEmpty(e.getValue()));
        playedReplays.entrySet().removeIf(e -> !usedReplays.containsKey(e.getKey()));
        sharedReplays.entrySet().removeIf(e -> !usedReplays.containsKey(e.getKey()));
        admiredReplays.entrySet().removeIf(e -> !usedReplays.containsKey(e.getKey()));
    }

    private boolean exist(Map.Entry<String, List<Integer>> entry) {
        String replayUid = entry.getKey();
        for (Integer group : entry.getValue()) {
            VideoGroupPO g = getNotCollectGroup(group);
            if (g != null && g.getVideo(replayUid) != null) {
                return true;
            }
        }
        return collectReplays.containsKey(replayUid);
    }

    private VideoGroupPO getNotCollectGroup(int groupId) {
        GDVideoHall.Group group = GDVideoHall.Group.getGroup(groupId);
        if (group == null) {
            return null;
        }
        return getNotCollectGroup(group);
    }

    public VideoGroupPO getGroup(Role role, GDVideoHall.Group group) {
        if (group == GDVideoHall.Group.MY_COLLECT) {
            return role.getPvp().videoHall.getGroup(GDVideoHall.Group.MY_COLLECT);
        }
        return getNotCollectGroup(group);
    }

    private VideoGroupPO getGroup(Role role, int groupId) {
        GDVideoHall.Group group = GDVideoHall.Group.getGroup(groupId);
        return getGroup(role, group);
    }

    private synchronized VideoGroupPO getNotCollectGroup(GDVideoHall.Group group) {
        for (VideoGroupPO g : groups) {
            if (g.group == group.getId()) {
                return g;
            }
        }
        VideoGroupPO groupPO = new VideoGroupPO(group.getId());
        groups.add(groupPO);
        return groupPO;
    }

    public VideoPO getVideo(Role role, GDVideoHall.Group group, String replayUid) {
        return getGroup(role, group).getVideo(replayUid);
    }

    private PlayPO copyANewPlay(PlayPO oldPlay) {
        PlayPO play = new PlayPO();
        play.story = oldPlay.story;
        play.a = oldPlay.a;
        play.b = oldPlay.b;
        play.winner = oldPlay.winner;
        play.teamBegin = oldPlay.teamBegin;
        play.teamEnd = oldPlay.teamEnd;
        play.rounds = oldPlay.rounds;
        play.startTime = oldPlay.startTime;
        play.endTime = oldPlay.endTime;
        play.roundMax = oldPlay.roundMax;
        play.expiredDate = LocalDateTime.now().plusDays(365);
        return play;
    }

    public synchronized void addVideo(GameBody game, GDVideoHall.Group group, PlayPO playPO) {
        VideoGroupPO videoGroup = getNotCollectGroup(group);
        PlayPO newPlay = copyANewPlay(playPO);
        VideoPO video = new VideoPO(newPlay);
        List<Integer> list = usedReplays.computeIfAbsent(video.replayProfile.replayUid, e -> new ArrayList<>());
        if (!list.contains(videoGroup.group)) {
            list.add(videoGroup.group);
            game.facilities.getMongo().play.save(newPlay);
        }
        addGroupVideo(game, videoGroup, video);
    }

    private void addGroupVideo(GameBody game, VideoGroupPO videoGroup, VideoPO video) {
        String oldReplayUid = videoGroup.addVideo(video);
        if (StringUtils.isNotBlank(oldReplayUid)) {
            checkAndDel(game, videoGroup.group, oldReplayUid);
        }
    }

    private void checkAndDeleteReplay(GameBody game, String replayUid) {
        List<Integer> groupIds = usedReplays.get(replayUid);
        if (groupIds == null || (groupIds.isEmpty() && CollectionUtils.isEmpty(collectReplays.get(replayUid)))) {
            game.facilities.getMongo().play.deleteById(replayUid);
            playedReplays.remove(replayUid);
            sharedReplays.remove(replayUid);
            admiredReplays.remove(replayUid);
        }
    }

    public void addPopularVideo(Role role, String replayUid) {
        VideoGroupPO videoGroup = getNotCollectGroup(GDVideoHall.Group.POPULAR);
        VideoPO video = getUsedVideo(role, replayUid);
        if (video == null) {
            return;
        }
        addGroupVideo(role.getGame(), videoGroup, video);
        checkAndAddUsed(GDVideoHall.Group.POPULAR, replayUid);
    }

    private void checkAndDel(GameBody game, int group, String replayUid) {
        List<Integer> groupIds = usedReplays.get(replayUid);
        if (CollectionUtils.isNotEmpty(groupIds)) {
            groupIds.removeIf(e -> e == group);
        }
        if (groupIds.isEmpty()) {
            usedReplays.remove(replayUid);
        }
        checkAndDeleteReplay(game, replayUid);
    }

    private void checkAndAddUsed(GDVideoHall.Group group, String replayUid) {
        List<Integer> list = usedReplays.computeIfAbsent(replayUid, e -> new ArrayList<>());
        if (!list.contains(group.getId())) {
            list.add(group.getId());
        }
    }

    private boolean replayExist(String replayUid) {
        return CollectionUtils.isNotEmpty(usedReplays.get(replayUid)) || CollectionUtils.isNotEmpty(collectReplays.get(replayUid));
    }

    public int shareVideo(String replayUid) {
        if (replayExist(replayUid)) {
            int shareTimes = sharedReplays.getOrDefault(replayUid, 0) + 1;
            sharedReplays.put(replayUid, shareTimes);
            return shareTimes;
        }
        return 0;
    }

    public int playVideo(String replayUid) {
        if (replayExist(replayUid)) {
            int playTimes = playedReplays.getOrDefault(replayUid, 0) + 1;
            playedReplays.put(replayUid, playTimes);
            return playTimes;
        }
        return 0;
    }

    public synchronized int admireVideo(Role role, String replayUid) {
        if (replayExist(replayUid)) {
            int admireTimes = admiredReplays.getOrDefault(replayUid, 0) + 1;
            admiredReplays.put(replayUid, admireTimes);
            if (admireTimes >= 5) {
                addPopularVideo(role, replayUid);
            }
            return admireTimes;
        }
        return 0;
    }

    public void loadVideoData(VideoGroupVO groupVO) {
        for (VideoVO video : groupVO.list) {
            video.admiredTimes = admiredReplays.getOrDefault(video.replayProfile.replayUid, 0);
            video.sharedTimes = sharedReplays.getOrDefault(video.replayProfile.replayUid, 0);
            video.playedTimes = playedReplays.getOrDefault(video.replayProfile.replayUid, 0);
        }
    }

    private synchronized void collectVideo(String roleUid, String replayUid) {
        final List<String> list = collectReplays.computeIfAbsent(replayUid, e -> new ArrayList<>());
        if (!list.contains(roleUid)) {
            list.add(roleUid);
        }
        checkAndAddUsed(GDVideoHall.Group.MY_COLLECT, replayUid);
    }

    private synchronized void cancelVideo(Role role, String replayUid) {
        if (!collectReplays.containsKey(replayUid)) {
            return;
        }
        final List<String> list = collectReplays.get(replayUid);
        list.remove(role.getUid());
        checkAndDel(role.getGame(), GDVideoHall.Group.MY_COLLECT.getId(), replayUid);
    }

    public GameResult<Void> collect(Role role, String replayUid) {
        GameResult<Void> r = new GameResult<>();
        VideoPO video = getNotCollectUsedVideo(replayUid);
        if (video == null) {
            return r.fail("录像已过期");
        }
        r = getGroup(role, GDVideoHall.Group.MY_COLLECT).collect(video);
        if (!r.ok()) {
            return r;
        }
        collectVideo(role.getUid(), replayUid);
        return r;
    }

    private VideoPO getNotCollectUsedVideo(String replayUid) {
        List<Integer> usedGroups = usedReplays.get(replayUid);
        if (CollectionUtils.isEmpty(usedGroups)) {
            return null;
        }
        VideoGroupPO group = getNotCollectGroup(usedGroups.get(0));
        if (group == null) {
            return null;
        }
        return group.getVideo(replayUid);
    }

    private VideoPO getUsedVideo(Role role, String replayUid) {
        List<Integer> usedGroups = usedReplays.get(replayUid);
        if (CollectionUtils.isEmpty(usedGroups)) {
            return null;
        }
        VideoGroupPO group = getGroup(role, usedGroups.get(0));
        if (group == null) {
            return null;
        }
        return group.getVideo(replayUid);
    }

    public GameResult<Void> cancelCollect(Role role, String replayUid) {
        GameResult<Void> r = getGroup(role, GDVideoHall.Group.MY_COLLECT).deleteVideo(replayUid);
        if (!r.ok()) {
            return r;
        }
        cancelVideo(role, replayUid);
        return r;
    }
}
